package compilers.AST.basics;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;

import compilers.AST.expression.Expression;
import compilers.AST.expression.Term;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableArrayElement;
import compilers.symbolTable.SymbolTableScope;

public class ScanFunctionCallTerm extends FunctionCallTerm {
	
	String fScanString;
	private Vector<Term> fParameters;

	public ScanFunctionCallTerm() {
		fParameters = new Vector<Term>();
	}
	
	public ScanFunctionCallTerm(String scanString, int lineNr) {
		super(lineNr);
		fScanString = scanString.substring(1, scanString.length()-1);
		fParameters = new Vector<Term>();
	}
	
	public void print(int identLevel) {
		println(identLevel, "ScanFunctionCallTerm");
		println(identLevel, "Scan string: " + fScanString);
		Iterator<Term> it = fParameters.iterator();
		while (it.hasNext()) {
			it.next().print(identLevel+1);
		}
	}
	
	public void addParameter(Term term) {
		fParameters.add(term);
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		return TypeSpecifier.INTEGER;
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		//Check if stdio is included
		if(currentScope.getElement("stdio.h", fNodeId) == null) {
			throw new SemanticalException("Function printf is not defined.", getLine());
		}
		
		int parsRequired = 0;
		int i = 0;
		int scanStringLength = fScanString.length();
		while(i < scanStringLength) {
			if(fScanString.charAt(i) == '%') {
				int tmp = i+1;
				boolean specifierFound = false;
				
				//Find the specifier (which comes after the length/width)
				while(tmp < scanStringLength) {
					if(!Character.isDigit(fScanString.charAt(tmp))) {
						specifierFound = true;
						break;
					}
					tmp = tmp+1;
				}

				if(!specifierFound) {
					throw new SemanticalException("No specifier given.", getLine());
				} else {
					//Check if we support the specifier
					char sp = fScanString.charAt(tmp);
					if(!((sp == 'd') || (sp == 'f') || (sp == 'i') || (sp == 's') || (sp == 'c'))) {
						throw new SemanticalException("Given specifier '" + sp + "' is not supported.", getLine());
					}
					
					//If supported, check if it is applicable for the given parameter
					int parType = fParameters.elementAt(parsRequired).getResultType(currentScope);
					switch(sp) {
						case 'd':
							if(!((parType == TypeSpecifier.INTEGER) || (parType == TypeSpecifier.FLOAT))) {
								throw new SemanticalException("Given specifier '" + sp + "' does not match with parameter.", getLine());
							}
							break;
						case 'i':
							if(!((parType == TypeSpecifier.INTEGER) || (parType == TypeSpecifier.FLOAT))) {
								throw new SemanticalException("Given specifier '" + sp + "' does not match with parameter.", getLine());
							}
							break;
						case 'f':
							if(!(parType == TypeSpecifier.FLOAT)) {
								throw new SemanticalException("Given specifier '" + sp + "' does not match with parameter.", getLine());
							}
							break;
						case 'c':
							if(!(parType == TypeSpecifier.CHAR)) {
								throw new SemanticalException("Given specifier '" + sp + "' does not match with parameter.", getLine());
							}
							break;
						case 's':
							if(!((parType == TypeSpecifier.CHAR) && (fParameters.elementAt(parsRequired).isArray(currentScope)))) {
								throw new SemanticalException("Given specifier '" + sp + "' does not match with parameter.", getLine());
							}
							break;
						default:
							break;
					}
				}
				
				parsRequired = parsRequired + 1;
			}
			i = i + 1;
		}
		int pars = fParameters.size();
		
		//Check whether there are just enough parameters given
		if(pars < parsRequired) {
			throw new SemanticalException("Not enough arguments given to call function 'scanf'.", getLine());
		} else if(pars > parsRequired) {
			throw new SemanticalException("Too many arguments given to call function 'scanf'.", getLine());
		}
		
		//Verify parameters
		Iterator<Term> parIt = fParameters.iterator();
		while(parIt.hasNext()) {
			parIt.next().checkSemantics(currentScope);
		}
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		int i = 0;
		int curParam = 0;
		int scanStringLength = fScanString.length();
		while(i < scanStringLength) {
			if(fScanString.charAt(i) == '%') {
				Term param = fParameters.elementAt(curParam);
				
				int specifierAt = i+1;
				
				//Find the specifier (which comes after the length/width)
				while(specifierAt < scanStringLength) {
					if(!Character.isDigit(fScanString.charAt(specifierAt))) {
						break;
					}
					specifierAt = specifierAt+1;
				}
				char sp = fScanString.charAt(specifierAt);
				
				//Determine width
				int width = 0;
				if(specifierAt != (i+1)) {
					width = Integer.parseInt(fScanString.substring(i+1, specifierAt));
				}

				//Code generation for scanning the parameter
				char type = ' ';
				switch(sp) {
					case 'd':
						type = 'i';
						break;
					case 'i':
						type = 'i';
						break;
					case 'c':
						type = 'c';
						break;
					case 'f':
						type = 'r';
						break;
					case 's':
						type='c';
						break;
				}
				
				// Read array of chars
				if (sp == 's') {
					// Get array from symbol table
					SymbolTableArrayElement element = (SymbolTableArrayElement) currentScope.getElement(param.getArrayIdent(currentScope), fNodeId);
					
					// Load relative start address on stack
					pGen.lod('a', currentScope.getDepth(), element.getMemoryAddress());
					pGen.conv('a', 'i');
					
					// Convert address to actual start address using nesting depths
					pGen.lda(currentScope.getDepth(), 0);
					pGen.conv('a', 'i');
					pGen.add('i');
					
					// Store actual start address at temp0
					pGen.storeAtAddress('i', 0);
					
					// Start reading characters and store them into the array
					String label = "scanString_" + fNodeId;
					pGen.load('i', "0");
					pGen.storeAtAddress('i', 1);
					pGen.beginLabel(label);					
					pGen.in('c');
					pGen.storeAtAddress('c', 2);
					pGen.loadFromAddress('c', 2);
					pGen.load('c', "27");
					pGen.nEquals('c');
					pGen.conditionalJump("end_" + label);
					pGen.loadFromAddress('i', 0);
					pGen.loadFromAddress('i', 1);
					pGen.add('i');
					pGen.conv('i', 'a');
					pGen.loadFromAddress('c', 2);
					pGen.sto('c');
					pGen.loadFromAddress('i', 1);
					pGen.inc('i', 1);
					pGen.storeAtAddress('i', 1);
					pGen.unconditionalJump("begin_" + label);
					pGen.endLabel(label);
					
				// Read single elements
				} else {
					// Read array element
					if (param instanceof ArrayTerm) {
						ArrayTerm element = (ArrayTerm) param;
						element.loadActualAddress(pGen, currentScope);
						pGen.in(type);
						pGen.sto(type);
					
					// Read normal variables
					} else {
						pGen.in(type);
						pGen.str(type, currentScope.getDepth(), param.getAddress(currentScope));
					}
				}
				
				i = specifierAt;
				curParam++;
			} else {
				pGen.load('c', Character.toString(fScanString.charAt(i)));
				pGen.out('c');
			}
			i = i + 1;
		}
	}
}
